﻿Imports System.Globalization

''' <summary>
''' アーカイブコンテナ
''' </summary>
''' <remarks>アイテムクラスアーカイブをまとめて管理するコンテナ</remarks>
Friend Class ArchiveContainer
    Implements IDirtyObjectConverter

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="logic">インスタンスをコレクションに変換するロジック</param>
    ''' <remarks></remarks>
    Public Sub New(logic As Func(Of Object, ICollection))
        Me.ToCollectionLogic = logic
    End Sub

    ''' <summary>
    ''' インスタンスをコレクションに変換するロジック
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Friend Property ToCollectionLogic As Func(Of Object, ICollection)

    Private _archives As Dictionary(Of String, ItemEntityArchive)
    ''' <summary>
    ''' クラスアーカイブ辞書
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Archives As Dictionary(Of String, ItemEntityArchive)
        Get
            If _archives Is Nothing Then _archives = New Dictionary(Of String, ItemEntityArchive)
            Return _archives
        End Get
    End Property

    ''' <summary>
    ''' ダーティかどうかを示します
    ''' </summary>
    ''' <param name="instance">検査対象インスタンス</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function IsDirty(instance As Object) As Boolean
        '現在のコレクション値を辞書化
        Dim dic = Me.ToDictionaryOrNothing(instance)

        '値がない場合の特殊判定式
        If dic Is Nothing Then
            Return If(Me.Archives.Any, True, False)
        End If

        '追加ダーティ（現在は存在するが、アーカイブに存在しない）
        If Me.ToAddInstances(dic).Any Then Return True

        '削除ダーティ（アーカイブに存在するが、現在は存在しない）
        If Me.ToDeleteArchives(dic).Any Then Return True

        '更新ダーティ（アーカイブされている場合、アーカイブに判定を依頼する）
        If Me.ToUpdateArchives(dic).Any Then Return True

        Return False
    End Function

#Region "クエリ"
    ''' <summary>
    ''' 追加されたインスタンスを取得します
    ''' </summary>
    ''' <param name="actuals"></param>
    ''' <returns></returns>
    ''' <remarks>インスタンスキーで比較</remarks>
    Private Function ToAddInstances(actuals As Dictionary(Of String, Object)) As IEnumerable(Of Object)
        Dim q = (From el In actuals Where Not Me.Archives.ContainsKey(el.Key) Select el.Value)
        Return q
    End Function

    ''' <summary>
    ''' 削除されたアーカイブを取得します
    ''' </summary>
    ''' <param name="actuals"></param>
    ''' <returns></returns>
    ''' <remarks>インスタンスキーで比較</remarks>
    Private Function ToDeleteArchives(actuals As Dictionary(Of String, Object)) As IEnumerable(Of ItemEntityArchive)
        Dim q = (From el In Me.Archives Where Not actuals.Keys.Contains(el.Key) Select el.Value)
        Return q
    End Function

    ''' <summary>
    ''' 存在する（追加でも削除でもない）アーカイブを取得します
    ''' </summary>
    ''' <param name="actuals"></param>
    ''' <returns></returns>
    ''' <remarks>ダーティ判定はされていない状態</remarks>
    Private Function ToExistArchives(actuals As Dictionary(Of String, Object)) As IEnumerable(Of ItemEntityArchive)
        Dim q = (From el In Me.Archives Where actuals.ContainsKey(el.Key) Select el.Value)
        Return q
    End Function

    ''' <summary>
    ''' 更新されたアーカイブを取得します
    ''' </summary>
    ''' <param name="actuals"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ToUpdateArchives(actuals As Dictionary(Of String, Object)) As IEnumerable(Of ItemEntityArchive)
        Dim act As Predicate(Of ItemEntityArchive) =
            Function(arch)
                Dim actual = actuals(arch.Key)
                Return If(arch.IsDirty(actual), True, False)
            End Function
        Dim q = (From el In Me.ToExistArchives(actuals) Where act.Invoke(el))
        Return q
    End Function
#End Region

    ''' <summary>
    ''' アーカイブ登録します
    ''' </summary>
    ''' <param name="instance"></param>
    ''' <remarks></remarks>
    Public Sub Regist(instance As Object)
        Me.Archives.Clear()

        '現インスタンスのコレクションを取得する
        Dim lst = Me.ToCollectionOrNothing(instance)
        If lst Is Nothing Then Return

        '現インスタンスにあるコレクションアイテムを全てアーカイブする
        For Each item In lst
            Dim ia As ItemEntityArchive = DirectCast(EntityArchiver.Create(item), ItemEntityArchive)
            If Me.Archives.ContainsKey(ia.Key) Then
                Dim msg = String.Format(My.Resources.KEY_VIOLATION_EXCEPTION, ia.Key)
                Throw New ArgumentException(msg)
            End If
            Me.Archives.Add(ia.Key, ia)
        Next
    End Sub

    ''' <summary>
    ''' 追加ダーティオブジェクトに変換する
    ''' </summary>
    ''' <param name="instance"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ToAddDirtyObjects(instance As Object) As ICollection(Of DirtyObject) Implements IDirtyObjectConverter.ToAddDirtyObjects
        Dim results As New List(Of DirtyObject)

        '現インスタンスのコレクションを取得する
        Dim lst = Me.ToCollectionOrNothing(instance)
        If lst Is Nothing Then Return results

        '現インスタンスにあるコレクションアイテムを全て追加ダーティに変換する
        For Each item In lst
            Dim ret = DirtyObjectCreater.ToAddDirtyObjects(item)
            results.AddRange(ret)
        Next

        Return results
    End Function

    ''' <summary>
    ''' 削除ダーティオブジェクトに変換する
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ToDeleteDirtyObjects() As ICollection(Of DirtyObject) Implements IDirtyObjectConverter.ToDeleteDirtyObjects
        Dim results As New List(Of DirtyObject)

        'アーカイブされているアイテムを全て削除ダーティに変換する
        For Each item In (From el In Me.Archives Select el.Value)
            Dim ret = item.ToDeleteDirtyObjects
            results.AddRange(ret)
        Next

        Return results
    End Function

    ''' <summary>
    ''' ダーティオブジェクトに変換する
    ''' </summary>
    ''' <param name="instance"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function ToDirtyObjects(instance As Object) As ICollection(Of DirtyObject) Implements IDirtyObjectConverter.ToDirtyObjects
        '現在のインスタンスのコレクションプロパティ値を辞書化
        Dim dic = Me.ToDictionaryOrNothing(instance)

        '値がない場合、アーカイブは全て削除ダーティに変換
        If dic Is Nothing Then
            Return Me.ToDeleteDirtyObjects
        End If

        Dim results As New List(Of DirtyObject)

        '追加判定されたコレクションアイテムは追加ダーティに変換
        For Each addObj In Me.ToAddInstances(dic)
            results.AddRange(DirtyObjectCreater.ToAddDirtyObjects(addObj))
        Next

        '削除判定されたアイテムアーカイブは削除ダーティに変換
        For Each delArch In Me.ToDeleteArchives(dic)
            results.AddRange(delArch.ToDeleteDirtyObjects)
        Next

        'それ以外のものは、更新されていないかを個別に判定
        For Each updArch In Me.ToExistArchives(dic)
            Dim c = dic.Item(updArch.Key)
            results.AddRange(updArch.ToDirtyObjects(c))
        Next

        Return results
    End Function

    ''' <summary>
    ''' オブジェクトをコレクションに変換します
    ''' </summary>
    ''' <param name="instance"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ToCollectionOrNothing(instance As Object) As ICollection
        Return Me.ToCollectionLogic.Invoke(instance)
    End Function

    ''' <summary>
    ''' オブジェクトを辞書に変換します
    ''' </summary>
    ''' <param name="instance"></param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function ToDictionaryOrNothing(instance As Object) As Dictionary(Of String, Object)
        Dim lst As ICollection = Me.ToCollectionOrNothing(instance)
        If lst Is Nothing Then Return Nothing

        Dim dic As New Dictionary(Of String, Object)

        'コレクションのアイテムをインスタンス辞書に変換します
        For Each item In lst
            dic.Add(KeyGenerator.Generate(item), item)
        Next

        Return dic
    End Function
End Class
